package io.shuttle.mbe.api

import io.shuttle.mbe.api.annotation.ChromeMinVersion
import io.shuttle.mbe.api.annotation.LowerEqMV3
import io.shuttle.mbe.api.types.Value1Function
import io.shuttle.mbe.api.types.VoidFunction
import io.shuttle.mbe.core.Promise

////////////////////
// User Scripts
////////////////////
/**
 * Use the `userScripts` API to execute user scripts in the User Scripts context.
 *
 * Permissions: "userScripts"
 * @since Chrome 120, MV3
 */
@ChromeMinVersion(120)
@LowerEqMV3
interface UserScripts {
    /**
     * Configures the `USER_SCRIPT` execution environment.
     *
     * @param properties - Contains the user script world configuration.
     * @param callback - Callback function to be executed after configuring the world.
     */
    fun configureWorld(properties: WorldProperties, callback: VoidFunction? = null): Promise<Void>

    /**
     * Returns all dynamically-registered user scripts for this extension.
     *
     * @param filter - If specified, this method returns only the user scripts that match it.
     * @param callback - Callback function to be executed after getting user scripts.
     * @returns A Promise that resolves with the same type that is passed to the callback.
     */
    fun getScripts(
        filter: UserScriptFilter,
        callback: Value1Function<List<RegisteredUserScript>>? = null
    ): Promise<List<RegisteredUserScript>>

    /**
     * Retrieves all registered world configurations.
     * @since Chrome 133
     */
    @ChromeMinVersion(133)
    fun getWorldConfigurations(callback: Value1Function<List<WorldProperties>>? = null): Promise<List<WorldProperties>>

    /**
     * Injects a script into a target context. By default, the script will be run at `document_idle`, or immediately if the page has already loaded. If the `injectImmediately` property is set, the script will inject without waiting, even if the page has not finished loading. If the script evaluates to a promise, the browser will wait for the promise to settle and return the resulting value.
     * @since Chrome 135
     */
    @ChromeMinVersion(135)
    fun execute(
        injection: UserScriptInjection,
        callback: Value1Function<List<InjectionResult>>? = null
    ): Promise<List<InjectionResult>>

    /**
     * Registers one or more user scripts for this extension.
     *
     * @param scripts - Contains a list of user scripts to be registered.
     * @param callback - Callback function to be executed after registering user scripts.
     */
    fun register(scripts: List<RegisteredUserScript>, callback: VoidFunction? = null): Promise<Void>

    /**
     * Resets the configuration for a user script world. Any scripts that inject into the world with the specified ID will use the default world configuration.
     * @param worldId The ID of the user script world to reset. If omitted, resets the default world's configuration.
     */
    fun resetWorldConfiguration(worldId: String?, callback: VoidFunction? = null): Promise<Void>

    /**
     * Unregisters all dynamically-registered user scripts for this extension.
     *
     * @param filter - If specified, this method unregisters only the user scripts that match it.
     * @param callback - Callback function to be executed after unregistering user scripts.
     * @returns A Promise that resolves with the same type that is passed to the callback.
     */
    fun unregister(filter: UserScriptFilter?, callback: VoidFunction? = null): Promise<Void>

    /**
     * Updates one or more user scripts for this extension.
     *
     * @param scripts - Contains a list of user scripts to be updated. A property is only updated for the existing script
     *                  if it is specified in this object. If there are errors during script parsing/file validation, or if
     *                  the IDs specified do not correspond to a fully registered script, then no scripts are updated.
     * @param callback - Callback function to be executed after updating user scripts.
     * @returns A Promise that resolves with the same type that is passed to the callback.
     */

    fun update(scripts: List<RegisteredUserScript>, callback: VoidFunction? = null): Promise<Void>

    enum class ExecutionWorld {
        MAIN,
        ISOLATED,
    }

    @ChromeMinVersion(135)
    data class InjectionResult(
        /** The document associated with the injection. */
        var documentId: String,
        /** The error, if any. `error` and `result` are mutually exclusive. */
        var error: String?,
        /** The frame associated with the injection. */
        var frameId: Number,
        /** The result of the script execution. */
        var result: Any
    )

    data class WorldProperties(
        /** Specifies the world csp. The default is the `ISOLATED` world csp. */
        var csp: String?,
        /** Specifies whether messaging APIs are exposed. The default is false.*/
        var messaging: Bookmark?,
        /**
         * Specifies the ID of the specific user script world to update. If not provided, updates the properties of the default user script world. Values with leading underscores (`_`) are reserved.
         * @since Chrome 133
         */
        var worldId: String?
    )

    data class UserScriptFilter(var ids: List<String>?)

    @ChromeMinVersion(135)
    data class InjectionTarget(
        /** Whether the script should inject into all frames within the tab. Defaults to false. This must not be true if `frameIds` is specified. */
        var allFrames: Boolean?,
        /** The IDs of specific documentIds to inject into. This must not be set if `frameIds` is set. */
        var documentIds: List<String>?,
        /** The IDs of specific frames to inject into. */
        var frameIds: List<Number>?,
        /** The ID of the tab into which to inject. */
        var tabId: Number
    )

    data class RegisteredUserScript(
        /** If true, it will inject into all frames, even if the frame is not the top-most frame in the tab. Each frame is checked independently for URL requirements; it will not inject into child frames if the URL requirements are not met. Defaults to false, meaning that only the top frame is matched. */
        var allFrames: Boolean,
        /** Specifies wildcard patterns for pages this user script will NOT be injected into. */
        var excludeGlobs: List<String>?,
        /**Excludes pages that this user script would otherwise be injected into. See Match Patterns for more details on the syntax of these strings. */
        var excludeMatches: List<String>?,
        /** The ID of the user script specified in the API call. This property must not start with a '_' as it's reserved as a prefix for generated script IDs. */
        var id: String,
        /** Specifies wildcard patterns for pages this user script will be injected into. */
        var includeGlobs: List<String>?,
        /** The list of ScriptSource objects defining sources of scripts to be injected into matching pages. This property must be specified for {@link register}, and when specified it must be a non-empty array.*/
        var js: List<ScriptSource>,
        /** Specifies which pages this user script will be injected into. See Match Patterns for more details on the syntax of these strings. This property must be specified for ${ref:register}. */
        var matches: List<String>?,
        /** Specifies when JavaScript files are injected into the web page. The preferred and default value is document_idle */
        var runAt: ExtensionTypes.RunAt?,
        /** The JavaScript execution environment to run the script in. The default is `USER_SCRIPT` */
        var world: ExecutionWorld?,
        /**
         * Specifies the user script world ID to execute in. If omitted, the script will execute in the default user script world. Only valid if `world` is omitted or is `USER_SCRIPT`. Values with leading underscores (`_`) are reserved.
         * @since Chrome 133
         */
        var worldId: String?
    )

    @ChromeMinVersion(135)
    data class UserScriptInjection(
        /** Whether the injection should be triggered in the target as soon as possible. Note that this is not a guarantee that injection will occur prior to page load, as the page may have already loaded by the time the script reaches the target. */
        var injectImmediately: Boolean?,
        /** The list of ScriptSource objects defining sources of scripts to be injected into the target. */
        var js: List<ScriptSource>,
        /** Details specifying the target into which to inject the script. */
        var target: InjectionTarget,
        /** The JavaScript "world" to run the script in. The default is `USER_SCRIPT`. */
        var world: ExecutionWorld,
        /** Specifies the user script world ID to execute in. If omitted, the script will execute in the default user script world. Only valid if `world` is omitted or is `USER_SCRIPT`. Values with leading underscores (`_`) are reserved. */
        var worldId: String,
    )

    data class ScriptSource(
        /** A string containing the JavaScript code to inject. Exactly one of file or code must be specified. */
        var code: String,
        /** The path of the JavaScript file to inject relative to the extension's root directory. Exactly one of file or code must be specified. */
        var file: String?
    )
}